// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Su Hao (suhao@baidu.com)
//
// Description: baidu_ca_coap

#include "baidu_ca_coap.h"
#include "ns_types.h"
#include "sn_nsdl.h"
#include "sn_coap_header.h"
#include "sn_nsdl_lib.h"
#include "baidu_ca_transport.h"
#include "baidu_ca_hashcode.h"
#include "baidu_ca_util_network.h"
#include "baidu_ca_nsdl_adapter.h"
#include "duer_log.h"

#define BCA_COAP_TCP_HDR  (0xbeefdead)

#define BCA_LIST_INC_SIZE (10)
//Note1: this value should only bigger than the default value
//Note2: this value setting should follow the value of MBEDTLS_SSL_MAX_CONTENT_LEN
//in src/iot-baidu-ca/include/baidu_ca_mbedtls_config.h
//Note3: pay attention to thread stack size which will call the function bca_coap_data_available
#define BCA_BUFFER_SIZE   (1024 * 2)

typedef struct _baidu_ca_coap_tcp_header_s {
    bca_u32_t       mask;
    bca_u32_t       size;
} bca_coap_hdr_t;

typedef struct _baidu_ca_dynres_s {
    bca_u32_t                  key;
    bca_notify_f               f_res;
    struct _baidu_ca_dynres_s* next;
} bca_dynres_t;

typedef struct _baidu_ca_coap_s {
    bca_status_t        retval;
    bca_trans_handler   trans;
    struct nsdl_s*      nsdl;
    bca_dynres_t*       list;
    bca_coap_result_f   f_result;
    bca_context         context;
    bca_addr_t          remote;
} bca_coap_t, *bca_coap_ptr;

typedef struct _baidu_ca_nsdl_map_s {
    bca_coap_ptr                 coap;
    struct _baidu_ca_nsdl_map_s* next;
} bca_nsdl_map_t;

BCA_LOC_IMPL bca_nsdl_map_t* s_bca_nsdl_map = NULL;

BCA_LOC const bca_dynres_t* bca_coap_dynamic_resource_find(bca_coap_ptr coap, bca_u32_t key);

BCA_LOC_IMPL void bca_coap_nsdl_add(bca_coap_ptr coap) {
    bca_nsdl_map_t* item = (bca_nsdl_map_t*)BCA_MALLOC(sizeof(bca_nsdl_map_t));

    if (item) {
        item->coap = coap;
        item->next = s_bca_nsdl_map;
        s_bca_nsdl_map = item;
    }
}

BCA_LOC_IMPL void bca_coap_nsdl_del(bca_coap_ptr coap) {
    bca_nsdl_map_t* item = s_bca_nsdl_map;
    bca_nsdl_map_t* p = NULL;

    while (item) {
        if (item->coap == coap) {
            if (p) {
                p->next = item->next;
            } else {
                s_bca_nsdl_map = item->next;
            }
            break;
        }

        p = item;
        item = item->next;
    }

    if (item) {
        BCA_FREE(item);
    }
}

BCA_LOC_IMPL bca_coap_ptr bca_coap_nsdl_get(struct nsdl_s* nsdl) {
    bca_nsdl_map_t* item = s_bca_nsdl_map;

    while (item) {
        if (item->coap && item->coap->nsdl == nsdl) {
            break;
        }
        item = item->next;
    }

    return item ? item->coap : NULL;
}

BCA_LOC_IMPL uint8_t bca_coap_dyn_res_callback(struct nsdl_s* handle,
                                               sn_coap_hdr_s* hdr,
                                               sn_nsdl_addr_s* addr,
                                               sn_nsdl_capab_e cap) {
    bca_coap_ptr coap = bca_coap_nsdl_get(handle);
    bca_status_t rs = BCA_ERR_INTERNAL;

    if (coap && hdr) {
        bca_u32_t key = bca_hashcode((char*)hdr->uri_path_ptr, hdr->uri_path_len,
                                     (bca_u32_t)coap);
        const bca_dynres_t* res = bca_coap_dynamic_resource_find(coap, key);

        if (res) {
            bca_msg_t msg;
            bca_addr_t ca_addr;
            bca_coap_header_set(&msg, hdr);
            bca_coap_address_set(&ca_addr, addr);
            ca_addr.type = coap->remote.type;
            rs = res->f_res(coap->context, &msg, &ca_addr);
        }
    }

    return rs;
}

BCA_LOC_IMPL uint8_t bca_coap_nsdl_tx(struct nsdl_s* nsdl, sn_nsdl_capab_e cap,
                                      uint8_t* data, uint16_t size, sn_nsdl_addr_s* addr) {
    bca_coap_ptr coap = bca_coap_nsdl_get(nsdl);
    uint8_t rs = 0;
    bca_addr_t addr_in;
    uint8_t* temp = NULL;

    if (!coap) {
        goto error;
    }

    if (coap->remote.type == BCA_PROTO_TCP) {
        temp = BCA_MALLOC(sizeof(bca_coap_hdr_t) + size);

        if (!temp) {
            DUER_LOGNW("bca_coap_nsdl_tx: memory overflow!!!");
            goto error;
        }

        bca_coap_hdr_t* ptr = (bca_coap_hdr_t*)temp;
        ptr->mask = bca_htonl(BCA_COAP_TCP_HDR);
        ptr->size = bca_htonl(size);
        BCA_MEMCPY(++ptr, data, size);
        data = temp;
        size += sizeof(bca_coap_hdr_t);
    }

    bca_coap_address_set(&addr_in, addr);
    coap->retval = bca_trans_send(coap->trans, data, size, addr ? &addr_in : NULL);

    if (coap->retval < 0) {
        goto error;
    } else {
        rs = 1; // rs > 0 means success
    }

exit:

    if (temp) {
        BCA_FREE(temp);
    }

    return rs;
error:
    rs = 0; // rs == 0 means fail
    goto exit;
}

BCA_LOC_IMPL uint8_t bca_coap_nsdl_rx(struct nsdl_s* nsdl, sn_coap_hdr_s* hdr,
                                      sn_nsdl_addr_s* addr) {
    bca_coap_ptr coap = bca_coap_nsdl_get(nsdl);
    uint8_t rs = 0;

    if (coap && coap->f_result) {
        bca_msg_t msg;
        bca_addr_t ca_addr;
        bca_coap_header_set(&msg, hdr);
        bca_coap_address_set(&ca_addr, addr);
        rs = coap->f_result(coap->context, coap, &msg, &ca_addr);
    }

    return rs;
}
void* bca_coap_nsdl_alloc(uint16_t size) {
    return BCA_MALLOC(size);
}

void bca_coap_nsdl_free(void* ptr) {
    BCA_FREE(ptr);
}

BCA_LOC_IMPL const char* bca_coap_convert_path(const char* path) {
    if (*path == '/') {
        path++;
    }

    return path;
}

BCA_LOC_IMPL const bca_dynres_t* bca_coap_dynamic_resource_find(
        bca_coap_ptr coap, bca_u32_t key) {
    const bca_dynres_t* p = NULL;

    if (coap && (p = coap->list) != NULL) {
        while (p && (p->key != key)) {
            p = p->next;
        }
    }

    return p;
}

BCA_LOC_IMPL bca_status_t bca_coap_dynamic_resource_add(bca_coap_ptr coap,
        const bca_res_t* res) {
    bca_dynres_t* dynres = NULL;
    bca_u32_t key;
    bca_status_t rs = BCA_ERR_INTERNAL;
    const char* path;

    if (!coap || !res) {
        goto error;
    }

    path = bca_coap_convert_path(res->path);
    key = bca_hashcode(path, BCA_STRLEN(path), (bca_u32_t)coap);

    if (bca_coap_dynamic_resource_find(coap, key) != NULL) {
        goto error;
    }

    dynres = BCA_MALLOC(sizeof(bca_dynres_t));

    if (!dynres) {
        goto error;
    }

    dynres->key = key;
    dynres->f_res = res->res.f_res;
    dynres->next = NULL;

    if (!coap->list) {
        coap->list = dynres;
    } else {
        dynres->next = coap->list;
        coap->list = dynres;
    }

    rs = BCA_NO_ERR;
exit:
    return rs;
error:

    if (dynres) {
        BCA_FREE(dynres);
    }

    goto exit;
}

BCA_LOC_IMPL bca_status_t bca_coap_dynamic_resource_remove(bca_coap_ptr coap,const char* path) {
    bca_u32_t key;
    bca_status_t rs = BCA_ERR_INTERNAL;
    bca_dynres_t* p = NULL;
    bca_dynres_t* q = NULL;

    if (!coap || !path || !coap->list) {
        goto exit;
    }

    path = bca_coap_convert_path(path);
    key = bca_hashcode(path, BCA_STRLEN(path), (bca_u32_t)coap);
    p = coap->list;

    while (p && p->key != key) {
        q = p;
        p = p->next;
    }

    if (!p) {
        goto exit;
    }

    if (p == coap->list) {
        coap->list = p->next;
    } else {
        q->next = p->next;
    }

    BCA_FREE(p);
    rs = BCA_NO_ERR;
exit:
    return rs;
}

BCA_LOC_IMPL void bca_coap_dynamic_resource_free(bca_coap_ptr coap) {
    bca_dynres_t* p = NULL;
    bca_dynres_t* q = NULL;

    if (!coap || !coap->list) {
        return;
    }

    p = coap->list;

    while (p) {
        q = p;
        p = p->next;
        BCA_FREE(q);
    }

    coap->list = NULL;
}

BCA_INT_IMPL bca_status_t bca_coap_resource_add(bca_coap_handler hdlr, const bca_res_t* res) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    bca_status_t rs = BCA_ERR_INTERNAL;

    if (coap && res) {
        sn_nsdl_resource_info_s sn_res;
        BCA_MEMSET(&sn_res, 0, sizeof(sn_res));
        sn_res.mode = (sn_nsdl_resource_mode_e)res->mode;
        sn_res.access = (sn_grs_resource_acl_e)res->allowed;
        sn_res.path = (uint8_t*)res->path;
        sn_res.pathlen = BCA_STRLEN(res->path);

        if (res->mode == BCA_RES_MODE_STATIC) {
            sn_res.resource = res->res.s_res.data;
            sn_res.resourcelen = res->res.s_res.size;
        } else {
            bca_coap_dynamic_resource_add(coap, res);
            sn_res.sn_grs_dyn_res_callback = bca_coap_dyn_res_callback;
        }

        if (sn_nsdl_create_resource(coap->nsdl, &sn_res) == SN_NSDL_SUCCESS) {
            rs = BCA_NO_ERR;
        }
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_coap_resource_remove(bca_coap_handler hdlr, const char* path) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    bca_status_t rs = BCA_ERR_INTERNAL;

    if (coap && path) {
        if (sn_nsdl_delete_resource(coap->nsdl, BCA_STRLEN(path),
                                    (uint8_t*)path) == SN_NSDL_SUCCESS) {
            bca_coap_dynamic_resource_remove(coap, path);
            rs = BCA_NO_ERR;
        }
    }

    return rs;
}

BCA_INT_IMPL bca_coap_handler bca_coap_acquire(bca_coap_result_f f_result,
        bca_context ctx, bca_context ctx_socket) {
    bca_coap_ptr coap = NULL;
    coap = BCA_MALLOC(sizeof(bca_coap_t));

    if (!coap) {
        DUER_LOGE("bca_coap_acquire: coap alloc failed");
        goto error;
    }

    BCA_MEMSET(coap, 0, sizeof(bca_coap_t));
    coap->trans = bca_trans_acquire(ctx_socket);

    if (!coap->trans) {
        DUER_LOGE("bca_coap_acquire: transport acquire failed");
        goto error;
    }

    coap->nsdl = sn_nsdl_init(bca_coap_nsdl_tx, bca_coap_nsdl_rx,
                              bca_coap_nsdl_alloc, bca_coap_nsdl_free);

    if (!coap->nsdl) {
        goto error;
    }

    bca_coap_nsdl_add(coap);
    coap->f_result = f_result;
    coap->context = ctx;
    goto exit;
error:
    bca_coap_release(coap);
    coap = NULL;
exit:
    return coap;
}

BCA_INT_IMPL bca_status_t bca_coap_connect(bca_coap_handler hdlr,
        const bca_addr_t* addr, const void* data, bca_size_t size) {
    bca_coap_ptr coap   = (bca_coap_ptr)hdlr;
    bca_status_t rs     = BCA_ERR_INTERNAL;
    bca_addr_t* pAddr  = NULL;

    if (!coap || !addr) {
        DUER_LOGE("bca_coap_connect: Invalid arguments.");
        goto exit;
    }

    bca_trans_set_pk(coap->trans, data, size);
    pAddr = &(coap->remote);

    if (pAddr->host) {
        BCA_FREE(pAddr->host);
        pAddr->host = NULL;
    }

    if (addr->host || addr->host_size > 0) {
        pAddr->host = BCA_MALLOC(addr->host_size);

        if (!pAddr->host) {
            goto exit;
        }

        BCA_MEMCPY(pAddr->host, addr->host, addr->host_size);
        pAddr->host_size = addr->host_size;
        pAddr->port = addr->port;
        pAddr->type = addr->type;
    }

    rs = bca_trans_connect(coap->trans, addr);

    if (rs < BCA_NO_ERR && rs != BCA_ERR_TRANS_WOULD_BLOCK) {
        DUER_LOGW("bca_coap_connect: transport connect failed by %d", rs);
    }
exit:
    return rs;
}

BCA_INT_IMPL bca_status_t bca_coap_disconnect(bca_coap_handler hdlr) {
    bca_coap_ptr coap   = (bca_coap_ptr)hdlr;
    return bca_trans_close(coap ? coap->trans : NULL);
}

BCA_INT_IMPL bca_status_t bca_coap_send(bca_coap_handler hdlr,
                                        const bca_msg_t* hdr) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    bca_status_t rs = BCA_ERR_INTERNAL;

    if (coap && coap->nsdl) {
        sn_coap_hdr_s coap_hdr;
        sn_nsdl_addr_s addr;
        sn_coap_options_list_s opt_list;
        bca_nsdl_address_set(&addr, &(coap->remote));
        bca_nsdl_header_set(&coap_hdr, &opt_list, hdr);
        coap->retval = BCA_ERR_INTERNAL;
        rs = sn_nsdl_send_coap_message(coap->nsdl, &addr, &coap_hdr);

        if (coap->retval <= 0) {
            if (coap->retval != BCA_ERR_TRANS_WOULD_BLOCK) {
                DUER_LOGW("bca_coap_nsdl_tx: sent = %d", coap->retval);
            }

            rs = coap->retval;
        }
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_coap_register(bca_coap_handler hdlr,
        const bca_coap_ep_t* ep) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    bca_status_t rs = BCA_ERR_INTERNAL;

    if (coap && coap->nsdl && ep) {
        sn_nsdl_ep_parameters_s endpoint;
        BCA_MEMSET(&endpoint, 0, sizeof(sn_nsdl_ep_parameters_s));
        endpoint.endpoint_name_ptr = (uint8_t*)ep->name_ptr;
        endpoint.endpoint_name_len = ep->name_len;
        endpoint.type_ptr = (uint8_t*)ep->type_ptr;
        endpoint.type_len = ep->type_len;
        endpoint.lifetime_ptr = (uint8_t*)ep->lifetime_ptr;
        endpoint.lifetime_len = ep->lifetime_len;
        endpoint.binding_and_mode = BINDING_MODE_U;

        if (set_NSP_address(coap->nsdl, (uint8_t*)"", 0,
                            SN_NSDL_ADDRESS_TYPE_IPV4) == 0) {
            coap->retval = BCA_ERR_INTERNAL;
            rs = sn_nsdl_register_endpoint(coap->nsdl, &endpoint);
            DUER_LOGV("sn_nsdl_register_endpoint: rs = %d, retval = %d", rs, coap->retval);

            if (coap->retval < 0) {
                rs = coap->retval;
            }
        }
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_coap_unregister(bca_coap_handler hdlr) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    bca_status_t rs = BCA_ERR_INTERNAL;

    if (coap && coap->nsdl) {
        coap->retval = BCA_ERR_INTERNAL;
        rs = sn_nsdl_unregister_endpoint(coap->nsdl);
        DUER_LOGV("sn_nsdl_unregister_endpoint: rs = %d, retval = %d", rs, coap->retval);

        if (coap->retval < 0) {
            rs = coap->retval;
        }
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_coap_update_registration(bca_coap_handler hdlr,
        bca_u32_t lifetime) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    bca_status_t rs = BCA_ERR_INTERNAL;

    if (coap && coap->nsdl) {
        uint8_t strlt[LIFETIME_MAX_LEN];
        bca_size_t len = BCA_SNPRINTF((char*)strlt, LIFETIME_MAX_LEN, "%d", lifetime);
        coap->retval = BCA_ERR_INTERNAL;
        rs = sn_nsdl_update_registration(coap->nsdl, strlt, len);
        DUER_LOGV("bca_coap_update_registration: rs = %d, retval = %d", rs,
                 coap->retval);

        if (coap->retval < 0) {
            rs = coap->retval;
        }
    }

    return rs;
}

/*
 * Receive the CoAP message
 *
 * @Param hdlr, in, the CoAP context
 * @Param data, in, the receive buffer
 * @Param size, in, the receive buffer size
 * @Return bca_status_t, the result
 */
BCA_LOC_IMPL bca_status_t bca_coap_recv(bca_coap_handler hdlr, void* data,
                                        bca_size_t size) {
    bca_status_t rs = BCA_ERR_INTERNAL;
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;

    if (coap) {
        if (coap->remote.type == BCA_PROTO_TCP) {
            bca_coap_hdr_t value = {0, 0};
            rs = bca_trans_recv(coap->trans, &value, sizeof(value), NULL);
            value.mask = bca_htonl(value.mask);
            value.size = bca_htonl(value.size);
            //TODO if recv size is smaller than sizeof(value), then enter an unknown situation
            if (rs != sizeof(value) || value.mask != BCA_COAP_TCP_HDR
                    || value.size > size) {
                if (rs != BCA_ERR_TRANS_WOULD_BLOCK) {
                    DUER_LOGI("bca_coap_recv: get header error rs = %d, mask = %x, value.size = %d",
                                rs, value.mask, value.size);
                }
                goto exit;
            }

            size = value.size;
        }

        rs = bca_trans_recv(coap->trans, data, size, NULL);

        if (rs > 0) {
            sn_nsdl_addr_s addr;
            bca_nsdl_address_set(&addr, &(coap->remote));
            rs = sn_nsdl_process_coap(coap->nsdl, data, rs, &addr);
        }
    }

exit:

    if (coap) {
        DUER_LOGV("bca_coap_recv: rs = %d", rs);
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_coap_data_available(bca_coap_handler hdlr) {
    bca_status_t rs = BCA_ERR_INTERNAL;
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    char data[BCA_BUFFER_SIZE];

    if (!coap) {
        goto exit;
    }

    rs = bca_coap_recv(coap, data, sizeof(data));

exit:
    return rs;
}

BCA_INT_IMPL bca_status_t bca_coap_exec(bca_coap_handler hdlr,
                                        bca_u32_t timestamp) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;

    if (coap) {
        sn_nsdl_exec(coap->nsdl, timestamp);
    }

    return BCA_ERR_INTERNAL;
}

BCA_INT_IMPL bca_status_t bca_coap_release(bca_coap_handler hdlr) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;

    if (coap) {
        bca_coap_nsdl_del(coap);

        if (coap->trans) {
            bca_trans_release(coap->trans);
            coap->trans = NULL;
        }

        if (coap->nsdl) {
            sn_nsdl_destroy(coap->nsdl);
            coap->nsdl = NULL;
        }

        bca_addr_t* pAddr = &(coap->remote);

        if (pAddr->host) {
            BCA_FREE(pAddr->host);
        }

        bca_coap_dynamic_resource_free(coap);
        BCA_FREE(coap);
        coap = NULL;
    }

    return BCA_NO_ERR;
}

BCA_INT_IMPL bca_status_t bca_coap_set_read_timeout(bca_coap_handler hdlr,
        bca_u32_t timeout) {
    bca_coap_ptr coap = (bca_coap_ptr)hdlr;
    return bca_trans_set_read_timeout(coap ? coap->trans : NULL, timeout);
}
